home *** CD-ROM | disk | FTP | other *** search
/ Clickx 65 / Clickx 65.iso / software / internet / xmarks / xmarks-3.1.1.xpi / chrome / content / foxmarks-nodes.js < prev    next >
Encoding:
Text File  |  2009-05-05  |  24.2 KB  |  777 lines

  1. /*
  2.  Copyright 2007-2008 Foxmarks Inc.
  3.  
  4.  foxmarks-nodes.js: implements class Node and Nodeset, encapsulating
  5.  our datamodel for the bookmarks.
  6.  
  7.  */
  8.  
  9. // To do:
  10. // * Add integrity checking to commands
  11. // * We're currently filtering out modified- and visited-only updates
  12. //   in the Compare algorithm. This is okay for modified but probably
  13. //   inappropriate for visited: visited will only get updated when some
  14. //   other attribute of the node changes, which may never happen. On the
  15. //   other hand, we don't want to sync every change to last visited. We
  16. //   may want to do something like a standard sync and a thorough sync;
  17. //   do a thorough sync either once a week or randomly 1 in 10 times. The
  18. //   thorough sync is when we'd perpetuate the last-visit updates.
  19. // * In merge algorithm, special treatment for toolbar folders.
  20.  
  21. // Module-wide constants
  22.  
  23. const NODE_ROOT     = "ROOT";
  24. const NODE_TOOLBAR  = "TOOLBAR";
  25. const PERMS_FILE    = 0644;
  26. const MODE_RDONLY   = 0x01;
  27. const MODE_WRONLY   = 0x02;
  28. const MODE_CREATE   = 0x08;
  29. const MODE_APPEND   = 0x10;
  30. const MODE_TRUNCATE = 0x20;
  31.  
  32. // class Node
  33.  
  34. function Node(nid, attrs) {
  35.     this.nid = nid;
  36.     for (a in attrs) {
  37.         if (attrs.hasOwnProperty(a)) this[a] = attrs[a];
  38.     }
  39.     // TODO: remove all the assumptions that ntype will be a bookmark
  40.     if (!this.ntype)
  41.         this.ntype = "bookmark";
  42.     //if (!this.ntype)
  43.     //    throw("Node.ntype now required");
  44. }
  45.  
  46. Node.prototype = {
  47.     constructor: Node,
  48.  
  49.     toSource: function() {
  50.         if (!this.ntype)
  51.             throw("Node.ntype now required");
  52.         return 'new Node("' + this.nid + '",' + 
  53.             this.GetSafeAttrs(true).toSource() + ')';
  54.     },
  55.  
  56.     GetSafeAttrs: function(withChildren) {
  57.         var attrs = {};
  58.  
  59.         for (var attr in this) {
  60.             if (this.hasOwnProperty(attr) && attr != 'nid' &&
  61.                 attr != 'private') {
  62.                 if (withChildren || attr != 'children') {
  63.                     attrs[attr] = this[attr];
  64.                 }
  65.             }
  66.         }
  67.  
  68.         return attrs;
  69.     },
  70.  
  71.     FindChild: function(nid) {
  72.         if (this["children"]) {
  73.             return this.children.indexOf(nid);
  74.         } else {
  75.             return -1;
  76.         }
  77.     }
  78. }
  79.  
  80. // class Nodeset
  81.  
  82. function Nodeset(datasource, cloneSource) {
  83.     if(datasource === undefined || datasource instanceof Nodeset)
  84.         throw("Nodeset() -- datasource is required");
  85.  
  86.     this.hash = null;
  87.     this._datasource = datasource;
  88.     this._cloneSource = cloneSource;
  89.     this._node = {};
  90.     this._callback = null;
  91.     this._length = cloneSource ? cloneSource.length : 0;
  92. }
  93.  
  94.  
  95.  
  96. Nodeset.FetchAdd = function(node) {
  97.     this.AddNode(node);
  98. }
  99.  
  100. Nodeset.FetchComplete = function(status) {
  101.     this._children = null;
  102.     this.callback(this.corrupt ? 1006 : status);
  103. }
  104.  
  105. var ct = {}
  106. Nodeset.Continue = {
  107.     notify: function(timer) {
  108.         var set = ct.self;
  109.         var nids = ct.nids;
  110.         var result;
  111.         var s = Date.now();
  112.         while (nids.length > 0 && Date.now() - s < 100) {
  113.             var next = nids.shift();
  114.             var nid = next[0];
  115.             var pnid = next[1];
  116.  
  117.             if (!set.Node(nid, false, true)) {
  118.                 LogWrite("Warning: OnTree() was about to reference " +
  119.                     nid + " which doesn't exist");
  120.                 break;
  121.             }
  122.  
  123.             try {
  124.                 result = ct.action.apply(ct.Caller, [nid, pnid]);
  125.             } catch (e) {
  126.                 if(typeof e == "number"){
  127.                     result = e;
  128.                 } else {
  129.                     LogWrite("OnTree error " + e.toSource());
  130.                     result = 3;
  131.                 }
  132.             }
  133.  
  134.             if (result)
  135.                 break;
  136.  
  137.             // if action above deleted nid...
  138.             if (set.Node(nid, false, true) == null)
  139.                 continue;
  140.  
  141.             if (set.Node(nid).ntype == "folder") {
  142.                 var children = set.Node(nid).children;
  143.                 var ix = 0;
  144.                 for (var child in children) {
  145.                     if (!children.hasOwnProperty(child))
  146.                         continue;
  147.                     if (ct.depthfirst) {
  148.                         nids.splice(ix++, 0, [children[child], nid]);
  149.                     } else {
  150.                         nids.push([children[child], nid]);
  151.                     }
  152.                 }
  153.             }
  154.         }
  155.  
  156.         if (nids.length > 0 && !result) {
  157.             timer.initWithCallback(Nodeset.Continue, 10,
  158.                 Ci.nsITimer.TYPE_ONE_SHOT);
  159.         } else {
  160.             ct.complete.apply(ct.Caller, [result]);
  161.         }
  162.     }
  163. }
  164.  
  165. Nodeset.prototype = {
  166.     constructor: Nodeset,
  167.  
  168.     get length() {
  169.         return this._length;
  170.     },
  171.  
  172.     NodeName: function(nid) {
  173.         var node = this.Node(nid, false, true);
  174.  
  175.         if (node && node.name) {
  176.             return node.name + "(" + nid + ")";
  177.         } else {
  178.             return nid;
  179.         }
  180.     },
  181.     handleNidConflict: function(lnode, snode, conflicts){
  182.         return this._datasource.handleNidConflict(lnode, snode, conflicts);
  183.     },
  184.         
  185.     AddNode: function(node) {
  186.         if (this._children && node.children) {
  187.             var self = this;
  188.             for (var index = 0; index < node.children.length; index++) {
  189.                 var cnid = node.children[index];
  190.                 if (self._children[cnid] != undefined) {
  191.                     node.children.splice(index--, 1);
  192.                     LogWrite("Warning: Filtering " + self.NodeName(cnid) + 
  193.                             " as a corrupted duplicate in parent " +
  194.                             node["name"] + " (" + node.nid + ")");
  195.                 } else {
  196.                     self._children[cnid] = true;
  197.                 }
  198.             }
  199.         }   
  200.             
  201.         if (this._node[node.nid]) { // Oh oh! Node already exists.
  202.             LogWrite("Warning: Node " + this.NodeName(node.nid) +
  203.                     " in folder " + this.NodeName(node.pnid) + 
  204.                     " already exists in folder " +
  205.                     this.NodeName(this._node[node.nid].pnid));
  206.             // Log error only; don't prevent sync as cleanup happened above
  207.             // this.corrupt = true;
  208.             return;
  209.         }
  210.         this._node[node.nid] = node;
  211.         this._length++;
  212.     },
  213.  
  214.     FetchFromNative: function(callback) {
  215.         this._children = {}
  216.         // this.source = new NativeDatasource();
  217.         this.callback = callback;
  218.         this._datasource.ProvideNodes(this, Nodeset.FetchAdd, Nodeset.FetchComplete);
  219.     },
  220.  
  221.     BaselineLoaded: function(baseline, callback) {
  222.         return this._datasource.BaselineLoaded(baseline, callback);
  223.     },
  224.     FlushToNative: function(callback) {
  225.         // var source = new NativeDatasource();
  226.         this._datasource.AcceptNodes(this, callback);
  227.         return;
  228.     },
  229.  
  230.     ProvideCommandset: function(callback) {
  231.         var self = this;
  232.         var cs = new Commandset();
  233.  
  234.         this.OnTree(Add, Done);
  235.         return;
  236.             
  237.         function Add(nid, pnid) {
  238.             cs.append(new Command("insert", nid,
  239.                 self.Node(nid).GetSafeAttrs()));
  240.             return 0;
  241.         }
  242.  
  243.         function Done(status) {
  244.             callback(status, cs);
  245.         }
  246.     },
  247.  
  248.     _GetFile: function() {
  249.         var file = Cc['@mozilla.org/file/directory_service;1']
  250.             .getService(Ci.nsIProperties)
  251.             .get('ProfD', Ci.nsIFile);
  252.  
  253.         file.append(this._datasource.getBaselineName());
  254.         return file;
  255.     },
  256.  
  257.     SaveToFile: function(callback) {
  258.  
  259.         var self = this;
  260.         var first = true;
  261.  
  262.         var file = this._GetFile();
  263.  
  264.         var fstream = Cc["@mozilla.org/network/safe-file-output-stream;1"]
  265.             .createInstance(Ci.nsIFileOutputStream);
  266.         fstream.init(file, (MODE_WRONLY | MODE_TRUNCATE | MODE_CREATE), 
  267.             PERMS_FILE, 0);
  268.  
  269.         var cstream = Cc["@mozilla.org/intl/converter-output-stream;1"]
  270.             .createInstance(Ci.nsIConverterOutputStream);
  271.         cstream.init(fstream, "UTF-8", 0, 0x0000);
  272.  
  273.         cstream.writeString('({ version:"'+ FoxmarksVersion() +
  274.                 '", currentRevision:' + self.currentRevision + 
  275.                 ', _node: {' );
  276.  
  277.         this.OnTree(WriteNode, WriteDone);
  278.         return;
  279.  
  280.         function WriteNode(nid, pnid) {
  281.             var node = self.Node(nid);
  282.             cstream.writeString((first ? "" : ",") + 
  283.                     "'" + nid.replace(/'/g, "\\'") + "'" + ":" + 
  284.                     node.toSource());
  285.             first = false;
  286.             return 0;
  287.         }
  288.  
  289.         function WriteDone(status) {
  290.             if (!status) {
  291.                 cstream.writeString("}})");
  292.                 // Flush the character converter, then finish the file stream,
  293.                 // guaranteeing that an existing file isn't overwritten unless
  294.                 // the whole thing succeeds.
  295.                 cstream.flush();            
  296.                 try {
  297.                     fstream.QueryInterface(Ci.nsISafeOutputStream).finish();
  298.                 } catch (e) {
  299.                     fstream.close();
  300.                     LogWrite("Error in Writing: " + e.message);
  301.                     status = 1009;
  302.                 }
  303.             }
  304.             callback(status);
  305.         }
  306.  
  307.     },
  308.     
  309.  
  310.     LoadFromFile: function() {
  311.         var file = this._GetFile();
  312.         var fstream = Cc["@mozilla.org/network/file-input-stream;1"]
  313.             .createInstance(Ci.nsIFileInputStream);
  314.         fstream.init(file, MODE_RDONLY, PERMS_FILE, 0);
  315.         var cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
  316.             .createInstance(Ci.nsIConverterInputStream);
  317.         cstream.init(fstream, "UTF-8", 32768, 0xFFFD);
  318.         var str = {}; 
  319.         
  320.         var contents = "";
  321.  
  322.         while (cstream.readString(32768, str) != 0) {
  323.             contents += str.value;
  324.         }
  325.         fstream.close();
  326.         cstream.close();
  327.  
  328.         var result = eval(contents);
  329.  
  330.         if (result["version"]) {
  331.             this._node = result._node;
  332.             this.currentRevision = result.currentRevision;
  333.             this.version = result.version;
  334.         } else {    // For backwards compatibility
  335.             this._node = result;
  336.             LogWrite("Baseline file has no currentRevision");
  337.             this.currentRevision = gSettings.GetSyncRevision(this._datasource.syncType);
  338.         }
  339.  
  340.         var self = this;
  341.         self._length = 0;
  342.         forEach(this._node, function() { self._length++; } );
  343.     },
  344.  
  345.  
  346.     Declone: function(callback) {
  347.         // If we are cloned from some other nodeset, copy any references
  348.         // we currently hold from the clonesource into ourselves and
  349.         // break the clonesource relationship.
  350.         // This must be done before serializing a nodeset to disk.
  351.  
  352.         var self = this;
  353.  
  354.         if (!this._cloneSource) {
  355.             callback(0);
  356.         } else {
  357.             this.OnTree(CopyNode, Done);
  358.         }
  359.         return;
  360.  
  361.         function CopyNode(nid, pnid) {
  362.             if (self._node[nid] === undefined) {
  363.                 self._node[nid] = self._cloneSource.Node(nid);
  364.             }
  365.             return 0;
  366.         }
  367.  
  368.         function Done(status) {
  369.             if (!status) {
  370.                 self._cloneSource = null;
  371.                 forEach(self._node, 
  372.                     function(v, k) { if (!v) delete self._node[k]; } ); 
  373.             }
  374.             callback(status);
  375.         }
  376.     },
  377.  
  378.     // Node returns the node with the given nid.
  379.     // If you intend to modify the returned node,
  380.     // set "write" true; this will do a "copy on write"
  381.     // from the clone source if one has been set.
  382.     // If node specified is not found, throws an exception
  383.     // unless "nullOkay" is true, in which case it returns null.
  384.  
  385.     Node: function(nid, write, nullOkay) {
  386.         if (nid in this._node) {
  387.             return this._node[nid];
  388.         } else if (this._cloneSource) {
  389.             var node = this._cloneSource.Node(nid, false, nullOkay);
  390.             if (!node || !write) {
  391.                 return node;
  392.             } else {
  393.                 var newNode = node.clone(true);
  394.                 if(newNode.private)
  395.                     delete newNode.private;
  396.                 this.AddNode(newNode);
  397.                 return newNode;
  398.             }
  399.         } else {
  400.             if (nullOkay)
  401.                 return null;
  402.             else
  403.                 throw Error("Node not found: " + nid);
  404.         }
  405.     },
  406.  
  407.     HasAncestor: function(nid, pnid) {
  408.         while (nid) {
  409.             var node = this.Node(nid, false, true);
  410.             if (!node) {
  411.                 LogWrite("Whoops! HasAncestor tried to reference " + nid +
  412.                         " which doesn't exist");
  413.                 throw Error("HasAncestor bad nid " + nid);
  414.             }
  415.             nid = node.pnid;
  416.             if (nid == pnid)
  417.                 return true;
  418.         }
  419.         return false;
  420.     },
  421.  
  422.     // Find next sibling in this folder that also exists in other's folder
  423.     NextSibling: function(nid, other) {
  424.         var pnid = this.Node(nid).pnid;
  425.         var oursibs = this.Node(pnid).children;
  426.         var othersibs = other.Node(pnid).children;
  427.  
  428.         for (var i = oursibs.indexOf(nid) + 1; i < oursibs.length; ++i) {
  429.             var sib = oursibs[i];
  430.             if (othersibs.indexOf(sib) >= 0) {
  431.                 return sib;
  432.             }
  433.         }
  434.  
  435.         return null;
  436.     },
  437.  
  438.     InsertInParent: function(nid, pnid, bnid) {
  439.         if (nid == NODE_ROOT && pnid == null) {
  440.             return; // Fail silently.
  441.         }
  442.  
  443.         if (!nid)
  444.             throw Error("bad nid");
  445.  
  446.         if (!pnid)
  447.             throw Error("bad pnid for nid " + nid);
  448.  
  449.         var parent = this.Node(pnid, true);
  450.         if (typeof parent["children"] == "undefined") {
  451.             parent.children = [];
  452.         }
  453.         if (parent.children.indexOf(nid) >= 0) {
  454.             throw Error("child " + nid + " already exists in parent " + pnid);
  455.         }
  456.         if (bnid) {
  457.             var i = parent.children.indexOf(bnid);
  458.             if (i >= 0) {
  459.                 parent.children.splice(i, 0, nid);
  460.             } else {
  461.                 throw Error("didn't find child " + bnid + " in parent " + pnid);
  462.             }
  463.         } else {
  464.             parent.children.push(nid);
  465.         }
  466.         this.Node(nid, true).pnid = pnid;
  467.     },            
  468.  
  469.     RemoveFromParent: function(nid) {
  470.         var node = this.Node(nid, true);
  471.         if (!node.pnid) {
  472.             LogWrite("node.pnid is undefined for " + node.name);
  473.         }
  474.         var parent = this.Node(node.pnid, true);
  475.         var i = parent.FindChild(nid);
  476.  
  477.         if (i >= 0) {
  478.             parent.children.splice(i, 1);
  479.         } else {
  480.             throw Error("didn't find child " + nid + " in parent " + pnid);
  481.         }
  482.         node.pnid = null;
  483.     },
  484.  
  485.     Do_insert: function(nid, args /* pnid, bnid, ntype, etc. */) {
  486. //        LogWrite("inserting " + nid + " " + args.toSource());
  487.         if (this.Node(nid, false, true) != null) {
  488.             var nargs = this.Node(nid).GetSafeAttrs();
  489.             var conflict = false;
  490.             forEach(args, function(value, attr) {
  491.                 if (nargs[attr] && value != nargs[attr]) {
  492.                     conflict = true;
  493.                 }
  494.             } );
  495.             if (conflict) {
  496.                 throw Error("Tried to insert a node that already exists");
  497.             } else {
  498.                 return; // In the interests of being accomodating, we're going
  499.                         // to let this one slide by. But make sure it doesn't
  500.                         // happen again, mkay?
  501.             }
  502.         }
  503.         var node = new Node(nid);
  504.  
  505.         for (attr in args) {
  506.             if (args.hasOwnProperty(attr) && 
  507.                     attr != 'pnid' && attr != 'bnid' && attr != 'children') {
  508.                 node[attr] = args[attr];
  509.             }
  510.         }
  511.  
  512.         this.AddNode(node);
  513.         this.InsertInParent(nid, args.pnid, args.bnid);
  514.     },
  515.  
  516.     Do_delete: function(nid) {
  517.         var self = this;
  518. //        LogWrite("deleting " + this.NodeName(nid));
  519.  
  520.         // Be careful here: only the top-level node has to be
  521.         // removed from its parent. That node and its descendants
  522.         // need to be nulled out.
  523.  
  524.         function NukeNode(nid) {
  525.             var node = self.Node(nid);
  526.             if (node.children) {
  527.                 for (var n = 0; n < node.children.length; ++n)
  528.                     NukeNode(node.children[n]);
  529.             }
  530.             if (self._cloneSource) {
  531.                 self._node[nid] = null; // If cloned, shadow deletion.
  532.             } else {
  533.                 delete self._node[nid]; // Otherwise, delete it outright.
  534.             }
  535.             self._length--;
  536.         }
  537.  
  538.         self.RemoveFromParent(nid);
  539.         NukeNode(nid);
  540.     },
  541.  
  542.     Do_move: function(nid, args /* pnid, bnid */) {
  543. //        LogWrite("moving " + this.NodeName(nid));
  544.  
  545.         this.RemoveFromParent(nid);
  546.         this.InsertInParent(nid, args.pnid, args.bnid);
  547.     },
  548.  
  549.     Do_reorder: function(nid, args /* bnid */) {
  550. //        LogWrite("reordering " + this.NodeName(nid) + " before " + 
  551. //                this.NodeName(args.bnid));
  552.         var pnid = this.Node(nid).pnid;
  553.         this.RemoveFromParent(nid);
  554.         this.InsertInParent(nid, pnid, args.bnid);
  555.     },
  556.  
  557.     Do_update: function(nid, args /* attrs */) {
  558. //        LogWrite("updating " + this.NodeName(nid));
  559.         var node = this.Node(nid, true);
  560.  
  561.         forEach(args, function(value, attr) {
  562.             if (value) {
  563.                 node[attr] = value;
  564.             } else {
  565.                 delete node[attr];
  566.             }
  567.         } );
  568.     },
  569.  
  570.     // Pass either a single command or a Commandset.
  571.     Execute: function(command) {
  572.         if (command instanceof Commandset) {
  573.             var self = this;
  574.             forEach(command.set, function(c) {
  575.                 self.Execute(c);
  576.             } );
  577.             return;
  578.         }
  579.  
  580.         var method = this["Do_" + command.action];
  581.         try {
  582.             method.apply(this, [command.nid, command.args]);
  583.         } catch (e) {
  584.             if(typeof e == "number") {
  585.                 throw e;
  586.             } else {
  587.                 Components.utils.reportError(e);
  588.                 throw Error("Failed executing command " + command.toSource() + 
  589.                         "; error is " + e.toSource());
  590.             }
  591.         }
  592.     },
  593.     OrderIsImportant: function(){
  594.         return this._datasource.orderIsImportant;
  595.     },
  596.  
  597.     // traverses this's bookmarks hierarchy starting with
  598.     // startnode, calling action(node) for each node in the tree,
  599.     // then calling complete() when traversal is done.
  600.     // enforces rules about maximum run times to prevent hanging the UI
  601.     // when traversing large trees or when running on slow CPU's.
  602.     // action() should return 0 to continue, non-zero status to abort.
  603.     // complete() is called with status, non-zero if aborted.
  604.     // depthfirst determines tree traversal order
  605.  
  606.     OnTree: function(action, complete, startnid, depthfirst) {
  607.         ct = {}
  608.         ct.self = this;
  609.         ct.Caller = this;
  610.         ct.action = action;
  611.         ct.complete = complete;
  612.         ct.startnid = startnid || NODE_ROOT;
  613.         ct.depthfirst = depthfirst;
  614.         ct.nids = [[ct.startnid, null]];
  615.         ct.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
  616.         ct.timer.initWithCallback(Nodeset.Continue, 10, 
  617.             Ci.nsITimer.TYPE_ONE_SHOT);
  618.         return;
  619.     },
  620.  
  621.     IGNORABLE: { created: true, visited: true, modified: true },
  622.  
  623.     // Compare this nodeset with another, returning a canonical list
  624.     // of commands that transforms this nodeset into the specified one.
  625.     // Note that at the successful conclusion of this routine, this
  626.     // nodeset will be transformed to match the specified nodeset.
  627.     Compare: function(other, callback) {
  628.         var self = this;
  629.         var commandset = new Commandset();
  630.  
  631.         Step1();
  632.         return;
  633.  
  634.         function Step1() {
  635.             self.OnTree(FindReordersInsertsMoves, Step2);
  636.         }
  637.  
  638.         function Step2(status) {
  639.             if (status) {
  640.                 callback(status);
  641.             } else {
  642.                 self.OnTree(FindDeletes, Step4);
  643.             }
  644.         }
  645.  
  646.         // There IS no step 3.
  647.  
  648.         function Step4(status) {
  649.             if (status) {
  650.                 callback(status);
  651.             } else {
  652.                 self.OnTree(FindUpdates, Step5);
  653.             }
  654.         }
  655.  
  656.         function Step5(status) {
  657.             if (status) {
  658.                 callback(status);
  659.             } else {
  660.                 callback(0, commandset);
  661.             }
  662.         }
  663.  
  664.         function FindReordersInsertsMoves(nid, pid) {
  665.             if (self.Node(nid).ntype != "folder")
  666.                 return 0;
  667.  
  668.             var snode = self.Node(nid);
  669.             var onode = other.Node(nid, false, true);
  670.             if (!onode) // Deleted; don't worry about children.
  671.                 return 0;
  672.  
  673.             var us = snode.children ? snode.children.slice() : [];
  674.             var them = onode.children ? onode.children.slice() : [];
  675.  
  676.             // Reduce us and them to intersections
  677.             us = us.filter(function(x) { return them.indexOf(x) >= 0; } );
  678.             them = them.filter(function(x) { return us.indexOf(x) >= 0; } );
  679.  
  680.             if (us.length != them.length) {
  681.                 LogWrite("Error: intersections of unequal length for " +
  682.                         self.NodeName(nid));
  683.                 LogWrite("us   = " + us);
  684.                 LogWrite("them = " + them);
  685.                 throw Error("Intersections of unequal length");
  686.             }
  687.  
  688.             // Reorder us according to them
  689.             if(self._datasource.orderIsImportant){
  690.                 for (var i = 0; i < us.length; ++i) {
  691.                     if (us[i] != them[i]) {
  692.                         var command = new Command("reorder", them[i], 
  693.                             { bnid: us[i] });
  694.                         commandset.append(command);
  695.                         self.Execute(command);
  696.                         // Simulate reorder in our intersected list
  697.                         us.splice(us.indexOf(them[i]), 1);
  698.                         us.splice(i, 0, them[i]);
  699.                     }
  700.                 }
  701.             }
  702.  
  703.  
  704.             // Walk through them to find inserts and moves
  705.             var sc = self.Node(nid).children || [];     // (May have changed)
  706.             var oc = onode.children || [];
  707.             forEach(oc, function(child, index) {
  708.                 if (sc.indexOf(child) < 0) {    // ... missing from us
  709.                     if (self.Node(child, false, true)) {    // ... but exists in set
  710.                         var command = new Command("move", child, 
  711.                             { pnid: nid, bnid: FindBnid(index + 1) } );
  712.                         commandset.append(command);
  713.                         self.Execute(command);
  714.                     } else {                                // ... missing entirely
  715.                         var attrs = other.Node(child).GetSafeAttrs();
  716.                         attrs.bnid = FindBnid(index + 1);
  717.                         var command = new Command("insert", child, attrs);
  718.                         commandset.append(command);
  719.                         self.Execute(command);
  720.                     }
  721.                 }
  722.  
  723.                 function FindBnid(index) {
  724.                     var oc = onode.children;
  725.                     var len = oc.length;
  726.                     while (index < len && us.indexOf(oc[index]) < 0) {
  727.                         ++index;
  728.                     }
  729.                     return index < len ? oc[index] : null;
  730.                 }
  731.             } );
  732.  
  733.             return 0;
  734.         }
  735.  
  736.         function FindDeletes(nid, pnid) {
  737.             if (!other.Node(nid, false, true)) {
  738.                 var command = new Command("delete", nid);
  739.                 commandset.append(command);
  740.                 self.Execute(command);
  741.             }
  742.             return 0;
  743.         }
  744.  
  745.         function FindUpdates(nid, pnid) {
  746.             var result = 0;
  747.             try {
  748.                 var snode = self.Node(nid);
  749.                 var onode = other.Node(nid);
  750.                 var attrs = {};
  751.                 if (self._datasource.compareNodes(snode, onode, attrs)) {
  752.                     var command = new Command("update", nid, attrs);
  753.                     commandset.append(command);
  754.                     self.Execute(command);
  755.                 }
  756.             } catch (e){
  757.                 if(typeof e != "number"){
  758.                     Components.utils.reportError(e);
  759.                     result = 4;
  760.                 } else {
  761.                     result = e;
  762.                 }
  763.             }
  764.             return result;
  765.         }
  766.  
  767.  
  768.     },
  769.  
  770.     Merge: function(source){
  771.         this._datasource.merge(this, source);
  772.     },
  773.  
  774.  
  775. };
  776.  
  777.